1   /*
2    * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.util;
27  
28  import java.lang.ref.SoftReference;
29  import java.util.Enumeration;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Map;
34  import java.util.MissingResourceException;
35  import java.util.Set;
36  import java.util.concurrent.ConcurrentHashMap;
37  import java.util.spi.TimeZoneNameProvider;
38  import sun.util.calendar.ZoneInfo;
39  import sun.util.resources.LocaleData;
40  import sun.util.resources.OpenListResourceBundle;
41  
42  /**
43   * Utility class that deals with the localized time zone names
44   */
45  public final class TimeZoneNameUtility {
46  
47      /**
48       * cache to hold time zone resource bundles. Keyed by Locale
49       */
50      private static ConcurrentHashMap<Locale, SoftReference<OpenListResourceBundle>> cachedBundles =
51          new ConcurrentHashMap<Locale, SoftReference<OpenListResourceBundle>>();
52  
53      /**
54       * cache to hold time zone localized strings. Keyed by Locale
55       */
56      private static ConcurrentHashMap<Locale, SoftReference<String[][]>> cachedZoneData =
57          new ConcurrentHashMap<Locale, SoftReference<String[][]>>();
58  
59      /**
60       * get time zone localized strings. Enumerate all keys.
61       */
62      public static final String[][] getZoneStrings(Locale locale) {
63          String[][] zones;
64          SoftReference<String[][]> data = cachedZoneData.get(locale);
65  
66          if (data == null || ((zones = data.get()) == null)) {
67              zones = loadZoneStrings(locale);
68              data = new SoftReference<String[][]>(zones);
69              cachedZoneData.put(locale, data);
70          }
71  
72          return zones;
73      }
74  
75      private static final String[][] loadZoneStrings(Locale locale) {
76          List<String[]> zones = new LinkedList<String[]>();
77          OpenListResourceBundle rb = getBundle(locale);
78          Enumeration<String> keys = rb.getKeys();
79          String[] names = null;
80  
81          while(keys.hasMoreElements()) {
82              String key = keys.nextElement();
83  
84              names = retrieveDisplayNames(rb, key, locale);
85              if (names != null) {
86                  zones.add(names);
87              }
88          }
89  
90          String[][] zonesArray = new String[zones.size()][];
91          return zones.toArray(zonesArray);
92      }
93  
94      /**
95       * Retrieve display names for a time zone ID.
96       */
97      public static final String[] retrieveDisplayNames(String id, Locale locale) {
98          OpenListResourceBundle rb = getBundle(locale);
99          return retrieveDisplayNames(rb, id, locale);
100     }
101 
102     private static final String[] retrieveDisplayNames(OpenListResourceBundle rb,
103                                                 String id, Locale locale) {
104         LocaleServiceProviderPool pool =
105             LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
106         String[] names = null;
107 
108         // Check whether a provider can provide an implementation that's closer
109         // to the requested locale than what the Java runtime itself can provide.
110         if (pool.hasProviders()) {
111             names = pool.getLocalizedObject(
112                             TimeZoneNameGetter.INSTANCE,
113                             locale, rb, id);
114         }
115 
116         if (names == null) {
117             try {
118                 names = rb.getStringArray(id);
119             } catch (MissingResourceException mre) {
120                 // fall through
121             }
122         }
123 
124         return names;
125     }
126 
127     private static final OpenListResourceBundle getBundle(Locale locale) {
128         OpenListResourceBundle rb;
129         SoftReference<OpenListResourceBundle> data = cachedBundles.get(locale);
130 
131         if (data == null || ((rb = data.get()) == null)) {
132             rb = LocaleData.getTimeZoneNames(locale);
133             data = new SoftReference<OpenListResourceBundle>(rb);
134             cachedBundles.put(locale, data);
135         }
136 
137         return rb;
138     }
139 
140     /**
141      * Obtains a localized time zone strings from a TimeZoneNameProvider
142      * implementation.
143      */
144     private static class TimeZoneNameGetter
145         implements LocaleServiceProviderPool.LocalizedObjectGetter<TimeZoneNameProvider,
146                                                                    String[]>{
147         private static final TimeZoneNameGetter INSTANCE =
148             new TimeZoneNameGetter();
149 
150         public String[] getObject(TimeZoneNameProvider timeZoneNameProvider,
151                                 Locale locale,
152                                 String requestID,
153                                 Object... params) {
154             assert params.length == 0;
155             String[] names = null;
156             String queryID = requestID;
157 
158             if (queryID.equals("GMT")) {
159                 names = buildZoneStrings(timeZoneNameProvider, locale, queryID);
160             } else {
161                 Map<String, String> aliases = ZoneInfo.getAliasTable();
162 
163                 if (aliases != null) {
164                     // Check whether this id is an alias, if so,
165                     // look for the standard id.
166                     if (aliases.containsKey(queryID)) {
167                         String prevID = queryID;
168                         while ((queryID = aliases.get(queryID)) != null) {
169                             prevID = queryID;
170                         }
171                         queryID = prevID;
172                     }
173 
174                     names = buildZoneStrings(timeZoneNameProvider, locale, queryID);
175 
176                     if (names == null) {
177                         // There may be a case that a standard id has become an
178                         // alias.  so, check the aliases backward.
179                         names = examineAliases(timeZoneNameProvider, locale,
180                                                queryID, aliases, aliases.entrySet());
181                     }
182                 }
183             }
184 
185             if (names != null) {
186                 names[0] = requestID;
187             }
188 
189             return names;
190         }
191 
192         private static String[] examineAliases(TimeZoneNameProvider tznp, Locale locale,
193                                                String id,
194                                                Map<String, String> aliases,
195                                                Set<Map.Entry<String, String>> aliasesSet) {
196             if (aliases.containsValue(id)) {
197                 for (Map.Entry<String, String> entry : aliasesSet) {
198                     if (entry.getValue().equals(id)) {
199                         String alias = entry.getKey();
200                         String[] names = buildZoneStrings(tznp, locale, alias);
201                         if (names != null) {
202                             return names;
203                         } else {
204                             names = examineAliases(tznp, locale, alias, aliases, aliasesSet);
205                             if (names != null) {
206                                 return names;
207                             }
208                         }
209                     }
210                 }
211             }
212 
213             return null;
214         }
215 
216         private static String[] buildZoneStrings(TimeZoneNameProvider tznp,
217                                     Locale locale, String id) {
218             String[] names = new String[5];
219 
220             for (int i = 1; i <= 4; i ++) {
221                 names[i] = tznp.getDisplayName(id, i>=3, i%2, locale);
222                 if (i >= 3 && names[i] == null) {
223                     names[i] = names[i-2];
224                 }
225             }
226 
227             if (names[1] == null) {
228                 // this id seems not localized by this provider
229                 names = null;
230             }
231 
232             return names;
233         }
234     }
235 }